iT邦幫忙

2024 iThome 鐵人賽

0
Mobile Development

Flutter 開發實戰 - 30 天逃離新手村系列 第 35

了解 Composited Layer 進行應用程式效能優化

  • 分享至 

  • xImage
  •  

在 Flutter 中,除了 WidgetElement、和 RenderObject 結構外,還有一個 Layer 結構。就像 Element 結構在建構階段產生 RenderObject 結構,RenderObject 結構則在渲染階段產生 Layer 結構,當 RenderObject.isRepaintBoundary 為 true 時會建立 Layer 。參考原始碼

我們用 React 來類比;

  • Widget = React Component,可以組成樹狀結構.
  • Element = React.element
  • RenderObject = 虛擬 DOM
  • Layer = 實際 DOM

也就是 Widget > Element > RenderObject > Layer。

Layer 是耗費效能的。文件說明如下:

使用 Layer 時,它們會導致 Rendering Pipeline 必須切換渲染目標(從一個 Layer 到另一個)。渲染目標的切換可能會刷新 GPU 的命令緩衝區,這表示原本可以批次處理獲得優化的效果會消失。渲染目標的切換還會產生大量記憶體需求,因為 GPU 需要將當前幀的內容從優化寫入的記憶體中局部複製出來,然後在切換回原來的 Layer 時再複製回去。

然而,通過將 UI 的部分內容隔離到獨立的 Layer 中,Flutter 可以在只有 UI 的一小部分發生變化時避免重新渲染整個畫面。Layer 有助於實現流暢的動畫效果,因為它們允許 UI 的某些部分獨立進行動畫,而不影響 UI 的其他部分。

因此,為了保持應用程式的 Layer 最佳化,請遵循以下規則:

  • 如果某個元素需要進行動畫,嘗試將其隔離到一個單獨的 Layer 中,這樣就不會造成過多的額外渲染
  • 另一方面,盡可能保持較少的 Layer 數量。每一個層級都會增加額外的計算和記憶體開銷

那麼具體該如何實現:

1. 避免過度渲染

最簡單的方式就是使用 RepaintBoundary 組件。它繼承自 SingleChildRenderObjectWidget ,表示這個組件有專屬的 RenderObject ,而這個 RenderObjectisRepaintBoundary 屬性設定為 true。使用 RepaintBoundary 可以輕鬆地將 UI 的某一部分隔離成獨立的 Layer。

RepaintBoundary(
  child: CircularProgressIndicator() // <- 動畫組件
)

這個範例我們限制了重新渲染的範圍,否則整個繪圖層都會重新渲染。

加入 RepaintBoundary 之後:

值得注意的是預設情況下,AppBar 也會在一個獨立的 Layer 中。如果我們檢查 AppBar 的程式碼,會發現它包含了 AnnotatedRegion ,而 AnnotatedRegion 對應的 RenderObjectalwaysNeedsCompositing 設定為 true.

這樣設計的原因是,但我們捲動 Scaffoldbody 時,AppBar 會固定不動,因此不會重新渲染。如果你自己客製化了自己的 AppBar 別忘了將它隔離在單獨的 Layer.

2. 避免過度 Layer

  1. 儘量避免在圖片使用 OpacityColorFiltered 組件。請使用 ImagecolorblendMode 參數。更多資訊可以參考 Common mistakes with Images in Flutter

  2. 不要在動畫中使用 OpacityAnimatedOpacity 的效能更好。

  3. 使用 decoration ,避免裁切 clippingsaveLayer 在舊的裝置上特別耗費效能,因為它會建立一個螢幕外的渲染目標,切換這些渲染目標可能耗費 1ms 的時間。即使沒有 saveLayer 裁切依舊非常耗費效能,它會影響後續所有的渲染操作。若想為組件增加形狀建議使用 DecoratedBoxContainerdecoration

  4. 儘量禁止裁切;有些組件預設啟用的裁切,若你確定對於佈局沒有什麼用處,例如組件永遠不會超出 Stack 那麼我們可以關閉

    Stack(
      clipBehavior: Clip.none,
      child: ...
    )
    

    另外,請注意 ListView 預設會為每個子項目加入重新渲染邊界。如果你確定列表的子項目不會各自獨立更新,可以在建構子中加入 addRepaintBoundaries: false 來停用此功能。更多關於優化 ListView 的資訊可以參考 Common mistakes with ListViews in Flutter

  5. 其他像 TransformBackdropFilterShaderMaskTexture 這樣的組件也會建立新的 Layer 可以多加注意。


上一篇
錯誤處理
下一篇
Shorebird - Flutter 版 Code Push
系列文
Flutter 開發實戰 - 30 天逃離新手村38
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言